package net.mgsx.product.t2m.modisco;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.mgsx.product.t2m.modisco.tools.ModiscoUtils;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.gmt.modisco.infra.common.core.logging.MoDiscoLogger;
import org.eclipse.gmt.modisco.infra.query.core.exception.ModelQueryExecutionException;
import org.eclipse.gmt.modisco.infra.query.core.java.IJavaModelQuery;
import org.eclipse.gmt.modisco.infra.query.core.java.ParameterValueList;
import org.eclipse.gmt.modisco.java.AbstractMethodDeclaration;
import org.eclipse.gmt.modisco.java.AbstractMethodInvocation;
import org.eclipse.gmt.modisco.java.MethodDeclaration;
import org.eclipse.gmt.modisco.java.Model;
import org.eclipse.gmt.modisco.java.Statement;

public class ImpactPercents implements IJavaModelQuery<MethodDeclaration, Double> {
	
	/**
	 * Compute method modification complexity over model
	 * @return complexity rate in percents
	 */
	public Double evaluate(final MethodDeclaration context, final ParameterValueList parameterValues)
			throws ModelQueryExecutionException {
		final Map<AbstractMethodDeclaration, CoverageInfo> mapInfo = new HashMap<AbstractMethodDeclaration, CoverageInfo>();
		scanModelRecursive(getModel(context), mapInfo);
		
		final List<AbstractMethodDeclaration> visitedMethods = new ArrayList<AbstractMethodDeclaration>();
		scanMethodRecursive(context, visitedMethods);
		
		int nbUsed = 0;
		int nbTotal = 0;
		for(AbstractMethodDeclaration md : mapInfo.keySet()){
			final CoverageInfo coverageInfo = mapInfo.get(md);
			if(visitedMethods.contains(md)){
				nbUsed += coverageInfo.nbInstruction;
			}
			nbTotal += coverageInfo.nbInstruction;
		}
		return (double)nbUsed / (double)nbTotal;
	}
	
	private static class CoverageInfo {
		private int nbInstruction;
	}
	
	private void scanModelRecursive(final Model model, final Map<AbstractMethodDeclaration, CoverageInfo> mapInfo)
	{
		for(AbstractMethodDeclaration md : ModiscoUtils.getElements(model, AbstractMethodDeclaration.class))
		{
			final CoverageInfo coverageInfo = new CoverageInfo();
			coverageInfo.nbInstruction = getNbInstructions(md);
			mapInfo.put(md, coverageInfo);
		}
		
	}
	
	private int getNbInstructions(final AbstractMethodDeclaration declaration){
		return ModiscoUtils.getElements(declaration, Statement.class).size();
	}
	
	private void scanMethodRecursive(final AbstractMethodDeclaration context, final List<AbstractMethodDeclaration> visitedMethods){
		if(context != null){
			visitedMethods.add(context);
			for (AbstractMethodInvocation invocation : context.getUsages()) {
				final AbstractMethodDeclaration caller = (AbstractMethodDeclaration)ModiscoUtils.getOwner(invocation, AbstractMethodDeclaration.class);
				if(caller == null){
					MoDiscoLogger.logWarning(ModiscoUtils.formatMessage("invocation is not in a method declaration", invocation), Activator.getDefault());
				}else if(!visitedMethods.contains(caller)){
					scanMethodRecursive(caller, visitedMethods);
				}
			}
		}
	}
	
	private Model getModel(final EObject object){
		return (Model)ModiscoUtils.getOwner(object, Model.class);
	}
	
}
